package com.mapforge.map.tilewriter;

import java.util.ArrayList;
import java.util.List;

import com.mapforge.exceptions.ChosenTileLayerException;
import com.mapforge.exceptions.EmptySelectionException;
import com.mapforge.exceptions.TileLayerStateException;
import com.mapforge.listener.TileLayerEditListener;
import com.mapforge.map.Mask;
import com.mapforge.map.Tile;
import com.mapforge.map.TileLayer;
import com.mapforge.map.TileMap;
import com.mapforge.map.Tileset;
import com.mapforge.selection.Selection;

/**
 * The {@link AbstractTileWriter} class exists to provide some basic
 * functionality for a {@link TileWriter} without actually implementing any tile
 * writing behavior, making it easier to implement {@link TileWriter} without
 * sacrificing flexibility.
 * 
 * Additionally, this class is a {@link MaskWriter}, meaning it handles editing
 * {@link TileMap} {@link Mask}.
 * 
 * @see TileWriter
 * @see MaskWriter
 * @author Will Morrison
 * 
 */
public abstract class AbstractTileWriter implements TileWriter {

	/** TileLayer this TileWriter considers the destination */
	private TileLayer layer;

	/** Tileset this TileWriter uses for input */
	private Tileset tileset;

	/** Collection of {@link TileLayerEditListener} interested in Tile edits */
	private List<TileLayerEditListener> tileLayerEditListeners;

	/**
	 * Indicates whether this tile writer is performing a preview of its
	 * behavior.
	 */
	private boolean isPreviewing;

	/**
	 * Indicates whether this tile writer is currently writing tiles to an
	 * underlying tile layer.
	 */
	private boolean isWriting;

	public AbstractTileWriter() {
		this.isPreviewing = false;
		this.isWriting = false;
		this.tileLayerEditListeners = new ArrayList<TileLayerEditListener>();
	}

	public void addTileLayerEditListener(TileLayerEditListener editListener) {
		this.tileLayerEditListeners.add(editListener);
	}

	public boolean removeTileLayerEditListener(TileLayerEditListener listener) {
		return tileLayerEditListeners.remove(listener);
	}

	public void clearTileLayerEditListeners() {
		tileLayerEditListeners.clear();
	}

	public void setTileset(Tileset tileset) {
		this.tileset = tileset;
	}

	/**
	 * {@link #validateWriteState(Selection, Selection)} checks that this
	 * {@link TileWriter} is in a valid state to write to its {@link TileLayer
	 * TileLayer.}
	 * 
	 * @param input
	 *            locations of {@link Tile tiles} in this {@link TileWriter tile
	 *            writer's} {@link TileSet} to be written to the underlying
	 *            {@link TileLayer}
	 * @param destination
	 *            selected area to write tiles from the selection into
	 * @throws ChosenTileLayerException
	 *             if there is a problem with the {@link TileLayer} selection
	 * @throws TileLayerStateException
	 *             if there is a problem with the state of this
	 *             {@link TileWriter tile writer's} {@link TileLayer}
	 * @throws EmptySelectionException
	 *             if input or destination is empty or null
	 * @throws IllegalStateException
	 *             if there's a problem with the state of the writer itself. If
	 *             this occurs the {@link TileWriter} is being handled properly.
	 */
	protected void validateWriteState(Selection input, Selection destination)
			throws ChosenTileLayerException, TileLayerStateException,
			EmptySelectionException, IllegalStateException {
		if (getTileLayer() == null) {
			throw new ChosenTileLayerException();
		} else if (!getTileLayer().isVisible()) {
			throw new TileLayerStateException("Selected layer is not visible.");
		} else if (getTileLayer().isLocked()) {
			throw new TileLayerStateException("Selected layer is locked.");
		} else if (input == null) {
			throw new EmptySelectionException("Input area was undefined.");
		} else if (input.isEmpty()) {
			throw new EmptySelectionException("Input area was empty.");
		} else if (destination == null) {
			throw new EmptySelectionException("Destination area was undefined.");
		} else if (destination.isEmpty()) {
			throw new EmptySelectionException("Destination area was empty.");
		} else if (!isWriting) {
			throw new IllegalStateException(
					"TileWriter was not informed of write. Be sure to call beginWrite.");
		}
	}

	public Tileset getTileset() {
		return tileset;
	}

	public void setTileLayer(TileLayer layer) {
		this.layer = layer;
	}

	public TileLayer getTileLayer() {
		return layer;
	}

	@Override
	public void beginPreview() {
		this.isPreviewing = true;
	}

	@Override
	public void endPreview() {
		this.isPreviewing = false;
	}

	@Override
	public void beginWrite() {
		this.isWriting = true;
	}

	public void endWrite() {
		this.isWriting = false;
	}

	public boolean isCurrentlyWriting() {
		return this.isWriting;
	}

	public boolean isPreviewing() {
		return this.isPreviewing;
	}
}
